using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using TomShane.Neoforce.Controls;
using UnitShared;
using tactics.Interfaces;
using tactics.GUI;
using tactics.Events;

namespace tactics
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    /// 

    public class TacticsGame
    {

        #region Class Members

        TacticsRenderer renderer;
        SpriteHelper spriteHelper;

        List<Player> players;
        List<UnitTemplate> units;
        AbilityLoader abilityLoader;
        AbilityHighlighter abilityHighlighter;

        StatusEffectLoader statusEffectLoader;
        MovementRules moveRules;

        GraphicsDevice graphics;
        GraphicsDeviceManager graphicsManager;

        TacticsCamera camera;

        Terrain terrain;
        TerrainMap terrainMap;

        SpriteFont basicFont;

        Cursor cursor;

        TurnManager turnManager;
        SelectionManager selectionManager;

        UnitCard unitCard;
        UnitCard targetCard;

        TurnCard turnCard;

        MiniDisplayManager miniDisplay;

        Manager guiManager;

        #endregion

        #region Properties

        public TerrainMap TerrainMap
        {
            get
            {
                return terrainMap;
            }
        }

        public AbilityLoader AbilityLoader
        {
            get
            {
                return abilityLoader;
            }
        }

        public StatusEffectLoader StatusEffectLoader
        {
            get
            {
                return statusEffectLoader;
            }
        }

        public TurnManager TurnManager
        {
            get
            {
                return turnManager;
            }
        }

        public TurnCard TurnCard
        {
            get
            {
                return turnCard;
            }
        }

        public List<Player> Players
        {
            get
            {
                return players;
            }
        }

        public TacticsCamera Camera
        {
            get
            {
                return camera;
            }
        }
        
        public SelectionManager Selection
        {
            get
            {
                return selectionManager;
            }
        }

        public MiniDisplayManager MiniDisplayManager
        {
            get
            {
                return miniDisplay;
            }
        }

        #endregion

        #region Constructor

        public TacticsGame()
        {
            guiManager = TacticsEngine.Instance.GuiManager;
            graphicsManager = TacticsEngine.Instance.GraphicsManager;

            LoadContent();
            Initialize();
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected void Initialize()
        {
            graphics = TacticsEngine.Instance.GraphicsManager.GraphicsDevice;

            //Cue cue = soundBank.GetCue("sword slash");
            //cue.Play();

            statusEffectLoader = new StatusEffectLoader(@"content/statusEffects.xml");
            statusEffectLoader.Load();

            abilityLoader = new AbilityLoader(@"content/abilities.xml");

            unitCard = new UnitCard(guiManager, abilityLoader.Abilities, @"content/gui/unitcard.xml", @"content/gui/statuscard.xml", @"content/gui/abilitycard.xml", UnitCard.LayoutOptions.Right);
            unitCard.UpdatePosition(2, 2);

            targetCard = new UnitCard(guiManager, abilityLoader.Abilities, @"content/gui/unitcard.xml", @"content/gui/statuscard.xml", @"content/gui/abilitycard.xml", UnitCard.LayoutOptions.Left);
            targetCard.UpdatePosition(graphicsManager.PreferredBackBufferWidth - targetCard.Width - 2, 2);

            renderer = new TacticsRenderer(graphics);
            GenerateNewTerrain(new MapBuilders.PlainsBuilder(53, 53, 1f));

            moveRules = new MovementRules(@"content/MovementTypes.xml");

            float scale = TerrainMap.Scale;
            int height = TerrainMap.Height;
            int width = TerrainMap.Width;

            camera = new TacticsCamera(graphicsManager,
                                        new Vector3(-7 * scale, 12 * scale, (height / 2) * scale),
                                        new Vector3((width / 2) - 5 * scale, 1 * scale, (height / 2) * scale));

            spriteHelper = new SpriteHelper(terrainMap.Scale);

            cursor = new Cursor(graphics, TacticsEngine.Instance.SpriteBatch, TacticsEngine.Instance.MouseTexture, camera, terrainMap);

            InitPlayers(2);

            turnManager = new TurnManager(players, moveRules, terrainMap);

            turnCard = new TurnCard(guiManager, @"content/gui/turnCard.xml");
            turnCard.Text = turnManager.CurrentPlayer.Name;
            turnCard.Colour = turnManager.CurrentPlayer.Colour;

            selectionManager = new SelectionManager(turnManager.GetUnitMoveMatrix, abilityHighlighter.GenerateHighlight);

            // first round is updated manually, this is for testing.
            // in a real game, there are no actions that have occured or statuses to
            // apply before the game actually begins
            miniDisplay.Update(players);
        }

        #endregion

        #region Content Loading

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected void LoadContent()
        {
            basicFont = TacticsEngine.Instance.Content.Load<SpriteFont>("Kootenay");
                        
            units = new List<UnitTemplate>();

            string [] xmlFiles = System.IO.Directory.GetFiles(@"content/units");
            foreach (string file in xmlFiles)
            {
                string shortFile = file.Substring(file.IndexOf("/") + 1);
                shortFile = shortFile.Replace(@".xnb", @"");
                units.Add(TacticsEngine.Instance.Content.Load<UnitTemplate>(shortFile));
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected void UnloadContent()
        {
        }

        public void RemoveGuiElements()
        {
            miniDisplay.Remove();
            unitCard.Remove();
            targetCard.Remove();
            turnCard.Remove();
        }

        #endregion

        #region Manual Initialization

        protected void InitPlayers(int numPlayers)
        {
            if (numPlayers < 2)
                numPlayers = 2;

            players = new List<Player>();

            for (int i = 0; i < numPlayers; ++i)
                players.Add(new Player(i, "Player " + (i + 1)));

            // randomize player teams
            DeploymentManager.InitializeEverythingAtRandom(players, units, terrainMap);

            // set units onto terrain
            foreach (var p in players)
                foreach (var u in p.Units)
                    u.worldPosition.Y = terrainMap.GetHeight(u.X, u.Y);

            miniDisplay = new MiniDisplayManager(guiManager, @"content/gui/minidisplay.xml", @"content/gui/statuscard.xml");
            for (int i = 0; i < players.Count; ++i)
                miniDisplay.AddPlayer(players[i]);
        }

        //Temp function for testing the hit event
        void Wizard_UnitHit(object sender, HitEventArgs e)
        {
            System.Console.WriteLine("{0} was hit!", e.Target.Name);
        }

        protected void GenerateNewTerrain(MapBuilders.IMapBuilder builder)
        {
            bool tempGrid = true;
            if (terrain != null)
             tempGrid = renderer.Grid;

            terrainMap = new TerrainMap(builder);
            terrain = new Terrain(terrainMap);

            renderer.Terrain = terrain;
            renderer.TerrainMap = terrainMap;

            renderer.Grid = tempGrid;

            if (turnManager != null)
            {
                turnManager.TerrainMap = terrainMap;
                turnManager.UpdateMoveMatrices();
                turnManager.UpdatePlayerMap();
            }

            abilityHighlighter = new AbilityHighlighter(terrainMap);

            // if selection manager exists, remake it to refresh its function pointers
            if (selectionManager != null)
                selectionManager = new SelectionManager(turnManager.GetUnitMoveMatrix, abilityHighlighter.GenerateHighlight);
        }

        #endregion

        #region Update per-frame code

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public void Update(GameTime gameTime, MouseState mouseState, MouseState oldMouseState)
        {
            UpdateSelection(mouseState, oldMouseState);
            camera.UpdateCamera(gameTime, mouseState, oldMouseState, Keyboard.GetState());
        }

        private void UpdateSelection(MouseState mouseState, MouseState oldMouseState)
        {
            if (selectionManager.Selection == SelectionType.Unit)
            {
                turnManager.UpdateUnitMovePath(selectionManager.UnitInFocus, cursor.GetHighlightCoords());
            }

            if (selectionManager.MouseUpdatesAllowed && TacticsEngine.Instance.MouseEnabled)
            {
                if (mouseState.RightButton == ButtonState.Pressed && oldMouseState.RightButton == ButtonState.Released)
                     selectionManager.RightClick();
                else
                {
                    Unit unitClicked = null;
                    Player unitOwner = null;
                    bool unitFound = false;

                    unitFound = GetPlayerUnitAt(cursor.GetHighlightCoords(), out unitClicked, out unitOwner);
                    
                    if (mouseState.RightButton == ButtonState.Released)
                        cursor.UpdateMouseOver(terrainMap);

                    if (unitFound)
                    {
                        if (mouseState.LeftButton == ButtonState.Pressed && oldMouseState.LeftButton == ButtonState.Released)
                            selectionManager.ClickUnit(unitClicked, unitOwner);
                        else if (mouseState.LeftButton == ButtonState.Released && mouseState.RightButton == ButtonState.Released)
                        {
                            selectionManager.HoverUnit(unitClicked, unitOwner);
                        }
                    }
                    else
                    {
                        if (mouseState.LeftButton == ButtonState.Pressed)
                            selectionManager.ClickNothing(cursor.GetHighlightCoords());
                        else
                            selectionManager.HoverNothing();
                    }
                }
            }

            unitCard.UnitFocus = selectionManager.UnitInFocus;
            Player owner = selectionManager.UnitInFocusOwner;
            if (owner != null)
                unitCard.NameColour = owner.Colour;

            targetCard.UnitFocus = selectionManager.TargetInFocus;
            owner = selectionManager.TargetInFocusOwner;
            if (owner != null)
                targetCard.NameColour = owner.Colour;

            unitCard.Visible = (unitCard.UnitFocus != null);

            if ((unitCard.UnitFocus != null && targetCard.UnitFocus != null) &&
                unitCard.UnitFocus != targetCard.UnitFocus)
                targetCard.Visible = true;
            else
                targetCard.Visible = false;
        }

        private bool GetPlayerUnitAt(Utility.Pair<int, int> coord, out Unit unit, out Player player)
        {
            bool found = false;

            unit = null;
            player = null;

            foreach (Player p in Players)
            {
                foreach (Unit u in p.Units)
                {
                    if (u.X == coord.First &&
                        u.Y == coord.Second)
                    {
                        player = p;
                        unit = u;
                        found = true;
                    }
                }
            }

            return found;
        }

        public void UpdateMiniPanels()
        {
            miniDisplay.Update(players);
        }

        #endregion

        #region Key Detection               

        public void OnKeyUp(Keys k)
        {
            // add functions that are necessary on KeyUp-only here
        }

        public void OnKeyPressed(Keys k)
        {
            if (k == TacticsEngine.Instance.Config.Keys["EndTurn"])
            {
                selectionManager.RightClick();
                // ensure unit is disabled even if it was only partially used this turn
                if (turnManager.UnitTurn != null)
                {
                    turnManager.UnitTurn.Acted = true;
                    turnManager.UnitTurn.Moved = true;
                }                
                turnManager.AdvanceTurn();
                miniDisplay.Update(players);
            }

            // debug keys
            if (k == Keys.T)
            {
                GenerateNewTerrain(new MapBuilders.PlainsBuilder(53, 53, 1f));
            }

            if (k == Keys.Y)
            {
                GenerateNewTerrain(new MapBuilders.CavesBuilder(53, 53, 1f));
            }

            if (k == Keys.I)
            {
                GenerateNewTerrain(new MapBuilders.SnowyFieldBuilder(53, 53, 1f));
            }

            if (k == Keys.U)
                UpdateMiniPanels();

            if (k == Keys.Enter)
            {
                selectionManager.RightClick();
                turnManager.EndTurn();
                miniDisplay.Update(players);
            }

            if (k == Keys.P)
            {
                abilityLoader.Abilities[3].UseAbility(players[0].Units[1]);

                miniDisplay.Update(players);
            }

            // pass key onto renderer for it to handle
            renderer.OnKeyPressed(k);
        }
        
        #endregion

        #region Rendering

        public void SetScreen()
        {
            camera.RecalcProjection();

            // layout recalc code
            miniDisplay.LayoutControls();
            targetCard.UpdatePosition(graphicsManager.PreferredBackBufferWidth - targetCard.Width - 2, 2);
            turnCard.UpdatePosition();
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public void Draw(GameTime gameTime)
        {
            renderer.Draw(  camera.CameraView, camera.CameraProjection, 
                            selectionManager.UnitInFocus, selectionManager.AbilityInUse,
                            selectionManager.GetHighlightArea(), turnManager.GetUnitMovePath(), cursor.GetHighlightCoords(), 
                            selectionManager.Selection);

            if (Mouse.GetState().RightButton != ButtonState.Pressed)
                cursor.DrawHighlight(selectionManager.UnitInFocus != null);

            spriteHelper.Draw(players, terrainMap, camera);
        }

        public void DrawCursor()
        {
            cursor.Draw();
        }

        #endregion        

        #region Convenience Methods

        public int AtCoord(Utility.Pair<int, int> coord)
        {
            return terrainMap.AtCoord(coord);
        }

        #endregion

    }
}